package com.flame.workflow.ru.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.flame.auth.core.context.AuthContextHolder;
import com.flame.auth.core.entity.TrustedPrincipal;
import com.flame.core.id.IdWorker;
import com.flame.core.utils.DateUtils;
import com.flame.core.utils.StringUtils;
import com.flame.workflow.constants.RuntimeConstants;
import com.flame.workflow.constants.TaskConstants;
import com.flame.workflow.constants.UserTaskConstants;
import com.flame.workflow.exception.WorkflowExcetionMessage;
import com.flame.workflow.exception.WorkflowRuntimeException;
import com.flame.workflow.id.entity.WorkflowOrganization;
import com.flame.workflow.id.entity.WorkflowUser;
import com.flame.workflow.id.service.IdentityService;
import com.flame.workflow.re.entity.Model;
import com.flame.workflow.re.entity.Node;
import com.flame.workflow.re.mapper.ModelMapper;
import com.flame.workflow.re.service.NodeService;
import com.flame.workflow.ru.controller.vo.*;
import com.flame.workflow.ru.engine.context.RuntimeContext;
import com.flame.workflow.ru.engine.event.RunTaskEvent;
import com.flame.workflow.ru.engine.event.StartInstanceEvent;
import com.flame.workflow.ru.entity.*;
import com.flame.workflow.ru.mapper.InstanceMapper;
import com.flame.workflow.ru.mapper.TaskCommentMapper;
import com.flame.workflow.ru.mapper.TaskPropertyMapper;
import com.flame.workflow.ru.trigger.TriggerTask;
import com.flame.workflow.ru.trigger.WorkflowTriggerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * flame_workflow_ru_instance 流程实例表
 *
 * @author 吴欣童
 * @since 2023-06-05 14:27:43
 */
@Service
public class InstanceService implements ApplicationContextAware {

    private static final Logger log = LoggerFactory.getLogger(InstanceService.class);

    private ApplicationContext applicationContext;

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Autowired
    private InstanceMapper instanceMapper;

    @Autowired
    private ModelMapper modelMapper;

    @Autowired
    private IdWorker idWorker;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private TaskService taskService;

    @Autowired
    private NodeService nodeService;

    @Autowired
    private TaskPropertyMapper taskPropertyMapper;

    @Autowired
    private UserTaskService userTaskService;

    @Autowired
    private TaskCommentMapper taskCommentMapper;

    @Autowired
    private IdentityService identityService;

    public List<Instance> pageInstance(InstanceVo instanceVo) {
        return instanceMapper.page(instanceVo);
    }

    public void updateInstance(Instance instance) {
        instanceMapper.updateById(instance);
    }

    public void startInstance(InstanceStartVo instanceStartVo) throws JsonProcessingException {
        Instance instance = new Instance();
        instance.setModelId(instanceStartVo.getModelId());
        instance.setModelVersion(instanceStartVo.getModelVersion());
        Map<String, Object> data = instanceStartVo.getData();
        Map<String, String> userDataMap = getInstanceUserDataMap();
        if (CollectionUtils.isEmpty(instanceStartVo.getData())) {
            instance.setFormData(objectMapper.writeValueAsString(userDataMap));
        } else {
            userDataMap.forEach((uk, uv) -> data.merge(uk, uv, (v1, v2) -> v1));
            instance.setFormData(objectMapper.writeValueAsString(data));
        }

        RuntimeContext runtimeContext = new RuntimeContext();
        runtimeContext.setInstance(instance);
        runtimeContext.setTrustedPrincipal(AuthContextHolder.getContext());
        eventPublisher.publishEvent(new StartInstanceEvent(runtimeContext));
    }

    public InstanceDetailVo getInstance(Long instanceId) {
        Instance instance = instanceMapper.findById(instanceId);

        if (ObjectUtils.isEmpty(instance)) {
            throw new WorkflowRuntimeException(WorkflowExcetionMessage.INSTANCE_NOT_FOUND);
        }
        WorkflowUser instanceUserInfo = identityService.getUserByUserId(instance.getUserId());
        WorkflowOrganization instanceOrgInfo = identityService.getOrgByOrgCode(instanceUserInfo.getOrgCode());
        WorkflowUser instanceSimpleUserInfo = new WorkflowUser();
        BeanUtils.copyProperties(instanceUserInfo, instanceSimpleUserInfo);
        instanceSimpleUserInfo.setOrgName(instanceOrgInfo.getOrgName());

        List<Task> taskList = taskService.listByInstanceId(instanceId);

        List<UserTask> userTaskList = userTaskService.listByInstanceId(instanceId);
        for (UserTask userTask : userTaskList) {
            WorkflowUser userInfo = new WorkflowUser();
            if (TriggerTask.USER_ID.equals(userTask.getUserId())) {
                userInfo.setUserId(TriggerTask.USER_ID);
                userInfo.setUserName("系统");
                userTask.setUserInfo(userInfo);
                continue;
            }
            WorkflowUser dbUserInfo = identityService.getUserByUserId(userTask.getUserId());
            if (!ObjectUtils.isEmpty(dbUserInfo)) {
                BeanUtils.copyProperties(dbUserInfo, userInfo);
                WorkflowOrganization orgInfo = identityService.getOrgByOrgCode(userInfo.getOrgCode());
                if (!ObjectUtils.isEmpty(orgInfo)) {
                    userInfo.setOrgName(orgInfo.getOrgName());
                }
            }
            userTask.setUserInfo(userInfo);
        }

        List<TaskComment> taskCommentList = taskCommentMapper.listByInstanceId(instanceId);
        for (TaskComment taskComment : taskCommentList) {
            WorkflowUser dbUserInfo = identityService.getUserByUserId(taskComment.getUserId());
            if (!ObjectUtils.isEmpty(dbUserInfo)) {
                WorkflowUser userInfo = new WorkflowUser();
                BeanUtils.copyProperties(dbUserInfo, userInfo);
                WorkflowOrganization orgInfo = identityService.getOrgByOrgCode(userInfo.getOrgCode());
                if (!ObjectUtils.isEmpty(orgInfo)) {
                    userInfo.setOrgName(orgInfo.getOrgName());
                }
                taskComment.setUserInfo(userInfo);
            }
        }
        InstanceDetailVo instanceDetailVo = new InstanceDetailVo();
        BeanUtils.copyProperties(instance, instanceDetailVo);
        instanceDetailVo.setUserInfo(instanceSimpleUserInfo);
        instanceDetailVo.setTaskList(taskList);
        instanceDetailVo.setUserTaskList(userTaskList);
        instanceDetailVo.setTaskCommentList(taskCommentList);
        return instanceDetailVo;
    }

    public void handleApproveTask(ApproveVo approveVo) {
        List<Long> taskList = approveVo.getTaskList();
        for (Long taskId : taskList) {
            Task task = taskService.getTask(taskId);
            Instance instance = instanceMapper.findById(task.getInstanceId());
            Node node = nodeService.getNode(instance.getModelId(), instance.getModelVersion(), task.getNodeId());
            TrustedPrincipal principal = AuthContextHolder.getContext();

            TaskProperty taskProperty = new TaskProperty();
            taskProperty.setInstanceId(instance.getInstanceId());
            taskProperty.setTaskId(taskId);
            taskProperty.setPropertyKey(TaskConstants.TASK_APPROVE_RESULT + principal.getAccount());
            taskProperty.setPropertyValue(approveVo.getApproveFlag());
            taskPropertyMapper.insert(taskProperty);

            taskProperty.setPropertyKey(TaskConstants.TASK_APPROVE_MESSAGE + principal.getAccount());
            taskProperty.setPropertyValue(approveVo.getMessage());
            taskPropertyMapper.insert(taskProperty);

            task.setTaskProperties(getTaskProperties(taskId));

            RuntimeContext runtimeContext = new RuntimeContext();
            runtimeContext.setInstance(instance);
            runtimeContext.setTask(task);
            runtimeContext.setNode(node);
            runtimeContext.setTrustedPrincipal(principal);
            eventPublisher.publishEvent(new RunTaskEvent(runtimeContext));
        }
    }

    private Map<String, String> getTaskProperties(Long taskId) {
        List<TaskProperty> taskPropertyList = taskPropertyMapper.listByTaskId(taskId);
        Map<String, String> map = new HashMap<>();
        for (TaskProperty taskProperty : taskPropertyList) {
            map.put(taskProperty.getPropertyKey(), taskProperty.getPropertyValue());
        }
        return map;
    }

    public void tempSubmit(InstanceStartVo instanceStartVo) throws JsonProcessingException {
        // 创建实例
        Instance instance = new Instance();
        instance.setModelId(instanceStartVo.getModelId());
        instance.setModelVersion(instanceStartVo.getModelVersion());
        Map<String, Object> data = instanceStartVo.getData();
        Map<String, String> userDataMap = getInstanceUserDataMap();
        if (CollectionUtils.isEmpty(instanceStartVo.getData())) {
            instance.setFormData(objectMapper.writeValueAsString(userDataMap));
        } else {
            userDataMap.forEach((uk, uv) -> data.merge(uk, uv, (v1, v2) -> v1));
            instance.setFormData(objectMapper.writeValueAsString(data));
        }

        Model dbModel = modelMapper.findById(instance.getModelId(), instance.getModelVersion());
        instance.setInstanceId(idWorker.nextId());
        instance.setInstanceName(dbModel.getModelName());
        instance.setInstanceStatus(RuntimeConstants.RUNTIME_STATUS_RUN);
        instance.setUserId(AuthContextHolder.getContext().getAccount());
        instance.setCreateTime(DateUtils.currentDateTime());

        instanceMapper.insert(instance);

        // 创建任务
        List<Node> nodeList = nodeService.listByParentId(instanceStartVo.getModelId(), instance.getModelVersion(), 0);
        Node node = nodeList.get(0);
        Task task = new Task();
        task.setInstanceId(instance.getInstanceId());
        task.setTaskId(idWorker.nextId());
        task.setTaskName(node.getNodeName());
        task.setTaskType(node.getNodeType());
        task.setNodeId(node.getNodeId());
        task.setParentTaskId(0L);
        task.setTaskStatus(RuntimeConstants.RUNTIME_STATUS_WAIT);
        task.setCreateTime(DateUtils.currentDateTime());
        taskService.addTask(task);

        // 创建用户任务
        userTaskService.addUserTask(instance.getInstanceId(), task.getTaskId(), instance.getUserId());
    }

    public void tempInstanceStart(TempInstanceStartVo tempInstanceStartVo) {
        Instance instance = instanceMapper.findById(tempInstanceStartVo.getInstanceId());
        Node node = nodeService.getNode(instance.getModelId(), instance.getModelVersion(), 1);
        Task task = taskService.getTaskByNodeId(instance.getInstanceId(), 1);
        TrustedPrincipal principal = AuthContextHolder.getContext();

        // 完成用户任务
        userTaskService.completeByTaskIdAndUserId(task.getTaskId(), principal.getAccount(),
                UserTaskConstants.USER_TASK_STATUS_COMPLETE_SUBMIT, false);

        // 启动流程
        RuntimeContext runtimeContext = new RuntimeContext();
        runtimeContext.setInstance(instance);
        runtimeContext.setNode(node);
        runtimeContext.setTask(task);
        runtimeContext.setTrustedPrincipal(principal);
        eventPublisher.publishEvent(new RunTaskEvent(runtimeContext));
    }

    public void tempSubmitMaintain(TempInstanceMaintainVo tempInstanceMaintainVo) throws JsonProcessingException {
        Instance instance = new Instance();
        instance.setInstanceId(tempInstanceMaintainVo.getInstanceId());
        if (CollectionUtils.isEmpty(tempInstanceMaintainVo.getData())) {
            instance.setFormData("{}");
        } else {
            instance.setFormData(objectMapper.writeValueAsString(tempInstanceMaintainVo.getData()));
        }
        instanceMapper.updateById(instance);
    }

    public Map<String, String> listTriggerTask() {
        Map<String, String> triggerTaskMap = new HashMap<>();
        Map<String, TriggerTask> beansOfType = applicationContext.getBeansOfType(TriggerTask.class);
        for (Map.Entry<String, TriggerTask> entry : beansOfType.entrySet()) {
            String beanName = entry.getKey();
            String desc = entry.getKey();
            TriggerTask value = entry.getValue();
            WorkflowTriggerTask triggerTaskAnnotation = value.getClass().getAnnotation(WorkflowTriggerTask.class);
            if (StringUtils.hasText(triggerTaskAnnotation.desc())) {
                desc = triggerTaskAnnotation.desc();
            }
            triggerTaskMap.put(beanName, desc);
        }
        return triggerTaskMap;
    }

    public void transferUserTask(TransferUserTaskVo transferUserTaskVo) {
        if (CollectionUtils.isEmpty(transferUserTaskVo.getUserIdSet())) {
            return;
        }
        TrustedPrincipal principal = AuthContextHolder.getContext();
        // 修改当前用户任务状态
        userTaskService.completeByTaskIdAndUserId(transferUserTaskVo.getTaskId(), principal.getAccount(),
                UserTaskConstants.USER_TASK_STATUS_COMPLETE_TRANSFER, false);
        // 添加用户信息
        TaskComment taskComment = new TaskComment();
        taskComment.setCommentId(idWorker.nextId());
        taskComment.setInstanceId(transferUserTaskVo.getInstanceId());
        taskComment.setTaskId(transferUserTaskVo.getTaskId());
        taskComment.setUserId(principal.getAccount());
        taskComment.setMessage(transferUserTaskVo.getMessage());
        taskComment.setCreateTime(DateUtils.currentDateTime());
        taskCommentMapper.insert(taskComment);
        // 添加新用户任务
        addNewUserTask(transferUserTaskVo.getInstanceId(), transferUserTaskVo.getTaskId(),
                transferUserTaskVo.getUserIdSet(), transferUserTaskVo.getMessage());
    }

    public void cancelInstance(CancleInstanceVo cancleInstanceVo) {
        // 把当前未完成 task 改为取消状态
        List<Task> inCompleteTaskList = taskService.listIncompleteTaskByInstanceId(cancleInstanceVo.getInstanceId());
        for (Task task : inCompleteTaskList) {
            task.setTaskStatus(RuntimeConstants.RUNTIME_STATUS_CANCEL);
            taskService.updateTask(task);

            // 把当前未完成 userTask 删掉
            List<UserTask> userTaskList = userTaskService.listCreateUserTask(task.getTaskId());
            for (UserTask userTask : userTaskList) {
                userTaskService.completeByTaskIdAndUserId(userTask.getTaskId(), userTask.getUserId(), UserTaskConstants.USER_TASK_STATUS_COMPLETE_CANCEL, false);
            }
        }

        // 把 instance 改为取消状态
        Instance instance = new Instance();
        instance.setInstanceId(cancleInstanceVo.getInstanceId());
        instance.setInstanceStatus(RuntimeConstants.RUNTIME_STATUS_CANCEL);
        instanceMapper.updateById(instance);
    }

    public void rollbackToNode(RollbackToNodeVo rollbackToNodeVo) {
        // 修改任务配置：执行方式、回退至节点
        taskPropertyMapper.deleteByPropertyKey(rollbackToNodeVo.getTaskId(), TaskConstants.APPROVAL_REFUSED_TYPE);
        taskPropertyMapper.deleteByPropertyKey(rollbackToNodeVo.getTaskId(), TaskConstants.APPROVAL_REFUSED_TO_NODE_ID);
        TaskProperty taskProperty = new TaskProperty();
        taskProperty.setInstanceId(rollbackToNodeVo.getInstanceId());
        taskProperty.setTaskId(rollbackToNodeVo.getTaskId());
        taskProperty.setPropertyKey(TaskConstants.APPROVAL_REFUSED_TYPE);
        taskProperty.setPropertyValue(TaskConstants.APPROVAL_REFUSED_TYPE_RETURN_SPECIFIC_NODE);
        taskPropertyMapper.insert(taskProperty);

        taskProperty.setPropertyKey(TaskConstants.APPROVAL_REFUSED_TO_NODE_ID);
        taskProperty.setPropertyValue(String.valueOf(rollbackToNodeVo.getNodeId()));
        taskPropertyMapper.insert(taskProperty);

        // 发起拒绝任务
        ApproveVo approveVo = new ApproveVo();
        approveVo.setTaskList(Collections.singletonList(rollbackToNodeVo.getTaskId()));
        approveVo.setApproveFlag("0");
        approveVo.setMessage(rollbackToNodeVo.getRollbackMessage());
        handleApproveTask(approveVo);
    }

    public void taskAddUser(TaskAddUserVo taskAddUserVo) {
        if (CollectionUtils.isEmpty(taskAddUserVo.getUserIdSet())) {
            return;
        }
        // 设置多人审批方式为：会签
        taskPropertyMapper.updateTaskPropertyValue(taskAddUserVo.getTaskId(), TaskConstants.MULTI_APPROVAL_TYPE,
                TaskConstants.MULTI_APPROVAL_TYPE_ALL_SIGN);

        // 加人
        addNewUserTask(taskAddUserVo.getInstanceId(), taskAddUserVo.getTaskId(), taskAddUserVo.getUserIdSet(), taskAddUserVo.getMessage());
    }

    private void addNewUserTask(Long instanceId, Long taskId, Set<String> userIdSet, String message) {
        Instance instance = instanceMapper.findById(instanceId);
        String currentUserId = AuthContextHolder.getContext().getAccount();

        List<UserTask> userTaskList = userTaskService.listByTaskId(taskId);

        Set<String> dbTaskUserSet = userTaskList.stream().map(UserTask::getUserId).collect(Collectors.toSet());
        for (String userId : userIdSet) {
            // 不能添加当前用户，或者提交流程的用户
            if (!dbTaskUserSet.contains(userId) && !userId.equals(currentUserId) &&
                    !userId.equals(instance.getUserId())) {
                userTaskService.addUserTask(instanceId, taskId, userId);
            }
        }

        if (StringUtils.hasText(message)) {
            TaskComment taskComment = new TaskComment();
            taskComment.setInstanceId(instanceId);
            taskComment.setTaskId(taskId);
            taskComment.setCommentId(idWorker.nextId());
            taskComment.setUserId(AuthContextHolder.getContext().getAccount());
            taskComment.setMessage(message);
            taskComment.setCreateTime(DateUtils.currentDateTime());
            taskCommentMapper.insert(taskComment);
        }
    }

    private Map<String, String> getInstanceUserDataMap() {
        TrustedPrincipal context = AuthContextHolder.getContext();
        WorkflowUser userInfo = identityService.getUserByUserId(context.getAccount());
        Map<String, String> map = new HashMap<>();
        map.put("userId", userInfo.getUserId());
        map.put("userName", userInfo.getUserName());
        map.put("orgCode", userInfo.getOrgCode());
        map.put("sex", userInfo.getSex());
        map.put("nationality", userInfo.getNationality());
//        map.put("idType", userInfo.getIdType());
        map.put("birthday", userInfo.getBirthday());
        return map;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void taskCopySend(TaskCopySendVo taskCopySendVo) {
        if (CollectionUtils.isEmpty(taskCopySendVo.getUserIdSet())) {
            return;
        }
        // 抄送
        Instance instance = instanceMapper.findById(taskCopySendVo.getInstanceId());
        String currentUserId = AuthContextHolder.getContext().getAccount();

        List<UserTask> userTaskList = userTaskService.listByTaskId(taskCopySendVo.getTaskId());
        Set<String> dbTaskUserSet = userTaskList.stream().map(UserTask::getUserId).collect(Collectors.toSet());
        for (String userId : taskCopySendVo.getUserIdSet()) {
            if (!dbTaskUserSet.contains(userId) && !userId.equals(currentUserId) && !userId.equals(instance.getUserId())) {
                userTaskService.addUserTask(taskCopySendVo.getInstanceId(), taskCopySendVo.getTaskId(), userId);
                userTaskService.completeByTaskIdAndUserId(taskCopySendVo.getTaskId(), userId, UserTaskConstants.USER_TASK_STATUS_COMPLETE_SEND, false);
            }
        }

        if (StringUtils.hasText(taskCopySendVo.getMessage())) {
            TaskComment taskComment = new TaskComment();
            taskComment.setInstanceId(taskCopySendVo.getInstanceId());
            taskComment.setTaskId(taskCopySendVo.getTaskId());
            taskComment.setCommentId(idWorker.nextId());
            taskComment.setUserId(AuthContextHolder.getContext().getAccount());
            taskComment.setMessage(taskCopySendVo.getMessage());
            taskComment.setCreateTime(DateUtils.currentDateTime());
            taskCommentMapper.insert(taskComment);
        }
    }
}
